#pragma once

#include <utility>
#include <limits>

#ifndef kPI
#define kPI 3.14159265358979323846264338327950288419716939937510F
#endif

using UInt32 = unsigned int;
using UInt8 = unsigned char;
using SInt32 = signed int;
using UInt64 = unsigned long int;
using UInt16 = unsigned short;

const float kBiggestFloatSmallerThanOne = 0.99999994f;
const double kBiggestDoubleSmallerThanOne = 0.99999999999999989;

#define ENUM_FLAGS(T) \
inline T  operator  |(const T s, const T e) { return (T)((unsigned)s | e); } \
inline T &operator |=(T      &s, const T e) { return s = s | e; }            \
inline T  operator  &(const T s, const T e) { return (T)((unsigned)s & e); } \
inline T &operator &=(T      &s, const T e) { return s = s & e; }            \
inline T  operator  ^(const T s, const T e) { return (T)((unsigned)s ^ e); } \
inline T &operator ^=(T      &s, const T e) { return s = s ^ e; }            \
inline T  operator  ~(const T s)            { return (T)~(unsigned)s; }



//extern "C" {

	void DebugAssertIf(bool exp);
	void AssertIf(bool exp);

	void DebugAssert(bool value);
	void Assert(bool value);

	//---------------------------------------------
	// float convert
	// round
	int RoundfToInt(float f);
	UInt32 RoundfToIntPos(float f);
	float Roundf(float f);

	// floor
	double Floord(double f);
	float Floorf(float f);
	UInt32 FloorfToIntPos(float f);
	int FloorfToInt(float f);

	// ceil
	float Ceilf(float f);
	UInt32 CeilfToIntPos(float f);
	int CeilfToInt(float f);
	double Ceild(double f);

	// abs
	float Abs(float v);
	double Abs(double v);
	int Abs(int v);

	// min max
	float FloatMin(float a, float b);
	float FloatMax(float a, float b);

	// sqrt
	float SqrtImpl(float f);

	// sin power cos sign fastsqrt 
	float Sin(float f);
	float Pow(float f, float f2);
	float Cos(float f);
	float Sign(float f);
	float FastSqrt(float value);
	float FastInvSqrt(float f);
	float FastestInvSqrt(float f);

	// is not a nan
	bool IsNAN(float value);
	bool IsNAN(double value);

	// compare
	int CompareFloatRobustSignUtility(float A);
	bool CompareFloatRobust(float f0, float f1, int maxUlps = 10);
//}




template<class T>
inline T* Stride(T* p, size_t offset)
{
	return reinterpret_cast<T*>((char*)p + offset);
}


static void HalfToFloat(UInt16 src, float& dest)
{
	// Integer alias
	UInt32& bits = *reinterpret_cast<UInt32*>(&dest);

	// Based on Fabian Giesen's public domain half_to_float_fast3
	static const UInt32 magic = { 113 << 23 };
	const float& magicFloat = *reinterpret_cast<const float*>(&magic);
	static const UInt32 shiftedExp = 0x7c00 << 13; // exponent mask after shift

	// Mask out sign bit
	bits = src & 0x7fff;
	if (bits)
	{
		// Move exponent + mantissa to correct bits
		bits <<= 13;
		UInt32 exponent = bits & shiftedExp;
		if (exponent == 0)
		{
			// Handle denormal
			bits += magic;
			dest -= magicFloat;
		}
		else if (exponent == shiftedExp) // Inf/NaN
			bits += (255 - 31) << 23;
		else
			bits += (127 - 15) << 23;
	}

	// Copy sign bit
	bits |= (src & 0x8000) << 16;
}

inline bool IsPlusInf(float value) { return value == std::numeric_limits<float>::infinity(); }
inline bool IsMinusInf(float value) { return value == -std::numeric_limits<float>::infinity(); }

inline bool IsFinite(const float& value)
{
	// Returns false if value is NaN or +/- infinity
	UInt32 intval = *reinterpret_cast<const UInt32*>(&value);
	return (intval & 0x7f800000) != 0x7f800000;
}

inline bool IsFinite(const double& value)
{
	// Returns false if value is NaN or +/- infinity
	UInt64 intval = *reinterpret_cast<const UInt64*>(&value);
	return (intval & 0x7ff0000000000000LL) != 0x7ff0000000000000LL;
}

inline float InvSqrt(float p) { return 1.0F / sqrtf(p); }
inline float Sqrt(float p) { return sqrtf(p); }

inline float Lerp(float from, float to, float t)
{
	return to * t + from * (1.0F - t);
}




// Returns the t^2
template<class T>
T Sqr(const T& t)
{
	return t * t;
}

#define kDeg2Rad (2.0F * kPI / 360.0F)
#define kRad2Deg (1.F / kDeg2Rad)

inline float Deg2Rad(float deg)
{
	// TODO : should be deg * kDeg2Rad, but can't be changed, 
	// because it changes the order of operations and that affects a replay in some RegressionTests
	return deg / 360.0F * 2.0F * kPI;
}

inline float Rad2Deg(float rad)
{
	// TODO : should be rad * kRad2Deg, but can't be changed, 
	// because it changes the order of operations and that affects a replay in some RegressionTests
	return rad / 2.0F / kPI * 360.0F;
}

inline double Roundf(double f)
{
	return Floord(f + 0.5);
}


///  Fast conversion of float [0...1] to 0 ... 65535
inline int NormalizedToWord(float f)
{
	f = FloatMax(f, 0.0F);
	f = FloatMin(f, 1.0F);
	return RoundfToIntPos(f * 65535.0f);
}

///  Fast conversion of float [0...1] to 0 ... 65535
inline float WordToNormalized(int p)
{
	AssertIf(p < 0 || p > 65535);
	return (float)p / 65535.0F;
}

///  Fast conversion of float [0...1] to 0 ... 255
inline int NormalizedToByte(float f)
{
	f = FloatMax(f, 0.0F);
	f = FloatMin(f, 1.0F);
	return RoundfToIntPos(f * 255.0f);
}

///  Fast conversion of float [0...1] to 0 ... 255
inline float ByteToNormalized(int p)
{
	AssertIf(p < 0 || p > 255);
	return (float)p / 255.0F;
}


// Returns float remainder for t / length
inline float Repeat(float t, float length)
{
	return t - Floorf(t / length) * length;
}

// Returns double remainder for t / length
inline double RepeatD(double t, double length)
{
	return t - floor(t / length) * length;
}

// Returns relative angle on the interval (-pi, pi]
inline float DeltaAngleRad(float current, float target)
{
	float delta = Repeat((target - current), 2 * kPI);
	if (delta > kPI)
		delta -= 2 * kPI;
	return delta;
}

// Returns true if the distance between f0 and f1 is smaller than epsilon
inline bool CompareApproximately(float f0, float f1, float epsilon = 0.000001F)
{
	float dist = (f0 - f1);
	dist = Abs(dist);
	return dist < epsilon;
}

/// CopySignf () returns x with its sign changed to y's.
inline float CopySignf(float x, float y)
{
	union
	{
		float f;
		UInt32 i;
	} u, u0, u1;
	u0.f = x; u1.f = y;
	UInt32 a = u0.i;
	UInt32 b = u1.i;
	SInt32 mask = 1 << 31;
	UInt32 sign = b & mask;
	a &= ~mask;
	a |= sign;

	u.i = a;
	return u.f;
}





